package com.cordwood.helper.localdb;

import ch.vorburger.exec.ManagedProcessException;
import ch.vorburger.mariadb4j.DB;
import ch.vorburger.mariadb4j.DBConfigurationBuilder;
import lombok.val;
import org.apache.commons.io.FileUtils;
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import javax.annotation.Nonnull;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

class EmbeddedDatabaseBootstrap implements ApplicationListener<ApplicationEnvironmentPreparedEvent> {

    private final Path MYSQL_DATA_PATH = Paths.get(".mysql-data").toAbsolutePath();

    private void bootstrap(int port, String database) throws IOException, ManagedProcessException, InterruptedException {
        killLastInstance();
        val config = DBConfigurationBuilder.newBuilder()
                .setPort(port)
                .setDataDir(MYSQL_DATA_PATH.toString())
                .build();
        val db = DB.newEmbeddedDB(config);
        db.start();
        waitUntilPortAvailable(port);
        db.createDB(database);
    }

    private void killLastInstance() throws IOException {
        val hostname = InetAddress.getLocalHost().getHostName();
        val pidFile = MYSQL_DATA_PATH.resolve(hostname + ".pid").toFile();
        if (pidFile.exists()) {
            val pid = FileUtils.readFileToString(pidFile, StandardCharsets.ISO_8859_1).trim();
            val osName = System.getProperty("os.name").toLowerCase();
            if (osName.contains("win")) {
                Runtime.getRuntime().exec("taskkill /pid " + pid);
            } else if(osName.contains("mac") || osName.contains("nix")) {
                Runtime.getRuntime().exec("kill -9 " + pid);
            }
        }
    }

    private void waitUntilPortAvailable(int port) throws InterruptedException {
        while (true) {
            try(val ignored = new Socket("127.0.0.1", port)) {
                return;
            } catch (IOException ignored) {
                TimeUnit.SECONDS.sleep(1);
            }
        }
    }

    @Override
    public void onApplicationEvent(@Nonnull ApplicationEnvironmentPreparedEvent event) {
        try {
            val connStr = Objects.requireNonNull(event.getEnvironment().getProperty("spring.datasource.url"));
            val uri = URI.create(connStr.substring(5));   // strip "jdbc:"
            val scheme = uri.getScheme();
            val host = uri.getHost();
            val port = uri.getPort();
            val database = Paths.get(uri.getPath()).getName(0).toString();
            //如果端口可用，直接退出函数
            if(!SocketUtils.isPortAvailable(port)){
                return;
            }
            if (scheme.equals("mysql")
                    && host.equalsIgnoreCase("localhost") || host.equals("127.0.0.1")
                    && SocketUtils.isPortAvailable(port)) {
                Assert.isTrue(
                        Objects.equals(event.getEnvironment().getProperty("spring.datasource.username"), "root") &&
                                !StringUtils.hasLength(event.getEnvironment().getProperty("spring.datasource.password")),
                        "User root with empty password must be used to start embedded database"
                );
                bootstrap(port, database);
            }
        } catch (IOException | ManagedProcessException | InterruptedException e) {
            throw new IllegalArgumentException(e);
        }
    }
}
